/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "s_local.h"


sndGlobals_t	snd;

alConfig_t		alConfig;

cvar_t *		s_logFile;
cvar_t *		s_singleSoundShader;
cvar_t *		s_singleEmitter;
cvar_t *		s_showUpdates;
cvar_t *		s_showEmitters;
cvar_t *		s_showChannels;
cvar_t *		s_showPortalChecks;
cvar_t *		s_skipStreaming;
cvar_t *		s_skipUpdates;
cvar_t *		s_skipEmitters;
cvar_t *		s_skipShakes;
cvar_t *		s_skipAttenuation;
cvar_t *		s_skipOcclusion;
cvar_t *		s_skipReverb;
cvar_t *		s_alDriver;
cvar_t *		s_ignoreALErrors;
cvar_t *		s_deviceName;
cvar_t *		s_masterVolume;
cvar_t *		s_effectsVolume;
cvar_t *		s_musicVolume;
cvar_t *		s_dopplerFactor;
cvar_t *		s_efxOcclusion;
cvar_t *		s_efxReverb;
cvar_t *		s_maxChannels;
cvar_t *		s_maxSoundsPerShader;
cvar_t *		s_maxSampleRate;


/*
 ==================
 S_CheckVariables
 ==================
*/
static void S_CheckVariables (void){

	if (s_masterVolume->modified){
		if (s_masterVolume->floatValue < 0.0f)
			CVar_SetFloat(s_masterVolume, 0.0f);
		else if (s_masterVolume->floatValue > 1.0f)
			CVar_SetFloat(s_masterVolume, 1.0f);

		s_masterVolume->modified = false;
	}

	if (s_effectsVolume->modified){
		if (s_effectsVolume->floatValue < 0.0f)
			CVar_SetFloat(s_effectsVolume, 0.0f);
		else if (s_effectsVolume->floatValue > 1.0f)
			CVar_SetFloat(s_effectsVolume, 1.0f);

		s_effectsVolume->modified = false;
	}

	if (s_musicVolume->modified){
		if (s_musicVolume->floatValue < 0.0f)
			CVar_SetFloat(s_musicVolume, 0.0f);
		else if (s_musicVolume->floatValue > 1.0f)
			CVar_SetFloat(s_musicVolume, 1.0f);

		s_musicVolume->modified = false;
	}

	if (s_dopplerFactor->modified){
		if (s_dopplerFactor->floatValue < 0.0f)
			CVar_SetFloat(s_dopplerFactor, 0.0f);
		else if (s_dopplerFactor->floatValue > 10.0f)
			CVar_SetFloat(s_dopplerFactor, 10.0f);

		s_dopplerFactor->modified = false;
	}
}

/*
 ==================
 S_CheckForErrors
 ==================
*/
static void S_CheckForErrors (void){

	int		error;
	char	*string;

	if ((error = qalGetError()) == AL_NO_ERROR)
		return;

	switch (error){
	case AL_INVALID_NAME:
		string = "AL_INVALID_NAME";
		break;
	case AL_INVALID_ENUM:
		string = "AL_INVALID_ENUM";
		break;
	case AL_INVALID_VALUE:
		string = "AL_INVALID_VALUE";
		break;
	case AL_INVALID_OPERATION:
		string = "AL_INVALID_OPERATION";
		break;
	case AL_OUT_OF_MEMORY:
		string = "AL_OUT_OF_MEMORY";
		break;
	default:
		string = "UNKNOWN ERROR";
		break;
	}

	Com_Error(false, "S_CheckForErrors: %s", string);
}

/*
 ==================
 S_PerformanceCounters
 ==================
*/
static void S_PerformanceCounters (void){

	if (s_showUpdates->integerValue)
		Com_Printf("emitterUpdates:%i\n", snd.pc.emitterUpdates);

	if (s_showChannels->integerValue)
		Com_Printf("channels:%i\n", snd.pc.channels);

	if (s_showPortalChecks->integerValue)
		Com_Printf("portals:%i\n", snd.pc.portals);

	// Clear for next frame
	memset(&snd.pc, 0, sizeof(performanceCounters_t));
}

/*
 ==================
 S_UpdateMixer
 ==================
*/
static void S_UpdateMixer (void){

	channel_t	*channel;
	int			i;

	if (s_logFile->integerValue)
		QAL_LogPrintf("---------- S_UpdateMixer ----------\n");

	// Set the AL state
	qalDistanceModel(AL_NONE);

	qalSpeedOfSound(343.3f);

	qalDopplerFactor(s_dopplerFactor->floatValue);
	qalDopplerVelocity(1.0f / METERS_PER_UNIT);

	// Update listener
	S_UpdateListener();

	// Update all channels
	for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
		if (!channel->sound)
			continue;

		snd.pc.channels++;

		S_UpdateChannel(channel);
	}

	// Update streaming
	S_UpdateStreaming();

	// Check for errors
	if (!s_ignoreALErrors->integerValue)
		S_CheckForErrors();

	// Look at the performance counters
	S_PerformanceCounters();
}

/*
 ==================
 S_PlayLocalSound
 ==================
*/
void S_PlayLocalSound (soundShader_t *soundShader){

	channel_t	*channel;

	// Pick a channel and play the sound
	channel = S_PickChannel(snd.listener.listenerId, SOUND_CHANNEL_ANY, soundShader, true, NULL);
	if (!channel)
		return;

	S_PlayChannel(channel);
}

/*
 ==================
 S_StopAllSounds
 ==================
*/
void S_StopAllSounds (void){

	channel_t	*channel;
	int			i;

	// Stop all channels
	for (i = 0, channel = snd.channels; i < snd.numChannels; i++, channel++){
		if (!channel->sound)
			continue;

		S_StopChannel(channel);
	}

	// Stop background music
	S_StopBackgroundMusic();
}

/*
 ==================
 S_Update
 ==================
*/
void S_Update (int frameTime){

	int		time;

	if (com_speeds->integerValue)
		time = Sys_Milliseconds();

	// Set frame time
	snd.frameTime = frameTime;

	// Log file
	if (s_logFile->modified){
		QAL_EnableLogging(s_logFile->integerValue);

		s_logFile->modified = false;
	}

	// Range check all modified variables
	S_CheckVariables();

	// Update mixer state and parameters
	S_UpdateMixer();

	// Log file
	if (s_logFile->integerValue > 0){
		QAL_LogPrintf("\n");

		CVar_SetInteger(s_logFile, s_logFile->integerValue - 1);
	}

	if (com_speeds->integerValue)
		com_timeSound += (Sys_Milliseconds() - time);
}

/*
 ==================
 S_GetALConfig

 Used by other subsystems to get the AL config
 ==================
*/
void S_GetALConfig (alConfig_t *config){

	if (!config)
		Com_Error(true, "S_GetALConfig: NULL config");

	*config = alConfig;
}

/*
 ==================
 S_Activate

 Called when the main window gains or loses focus.
 The window may have been destroyed and recreated between a deactivate and an
 activate.
 ==================
*/
void S_Activate (bool active){

	if (!alConfig.initialized)
		return;

	snd.active = active;
}

/*
 ==================
 S_ListSoundDevices_f
 ==================
*/
static void S_ListSoundDevices_f (void){

	const char	*ptr = alConfig.deviceList;

	while (*ptr){
		Com_Printf("%s\n", ptr);

		ptr += Str_Length(ptr) + 1;
	}
}

/*
 ==================
 S_SfxInfo_f
 ==================
*/
static void S_SfxInfo_f (void){

	Com_Printf("\n");
	Com_Printf("AL_VENDOR: %s\n", alConfig.vendorString);
	Com_Printf("AL_RENDERER: %s\n", alConfig.rendererString);
	Com_Printf("AL_VERSION: %s\n", alConfig.versionString);
	Com_Printf("AL_EXTENSIONS: %s\n", alConfig.extensionsString);
	Com_Printf("ALC_EXTENSIONS: %s\n", alConfig.alcExtensionsString);
	Com_Printf("\n");
	Com_Printf("AL_MAX_AUXILIARY_SENDS: %i\n", alConfig.maxAuxiliarySends);
	Com_Printf("\n");
	Com_Printf("DEVICE: %s\n", alConfig.deviceName);
	Com_Printf("MIXER: %i Hz (%s refresh)\n", alConfig.mixerFrequency, (alConfig.mixerSync) ? "synchronous" : Str_VarArgs("%i Hz", alConfig.mixerRefresh));
	Com_Printf("CHANNELS: %i\n", snd.numChannels);
	Com_Printf("CPU: %s\n", Sys_GetProcessorString());
	Com_Printf("\n");

	if (alConfig.efxAvailable){
		if (snd.efx.reverb)
			Com_Printf("Using EFX reverb effects\n");
		else
			Com_Printf("EFX reverb effects are disabled\n");

		if (snd.efx.occlusion)
			Com_Printf("Using EFX occlusion effects\n");
		else
			Com_Printf("EFX occlusion effects are disabled\n");
	}
	else {
		Com_Printf("EFX reverb effects not available\n");
		Com_Printf("EFX occlusion effects not available\n");
	}

	Com_Printf("\n");
}

/*
 ==================
 S_Register
 ==================
*/
static void S_Register (void){

	s_logFile = CVar_Get("s_logFile", "0", CVAR_CHEAT, "Number of frames to log AL calls");
	s_singleSoundShader = CVar_Get("s_singleSoundShader", "0", CVAR_CHEAT | CVAR_LATCH, "Use a single default sound shader on every emitter");
	s_singleEmitter = CVar_Get("s_singleEmitter", "-1", CVAR_CHEAT, "Only play the specified sound emitter");
	s_showUpdates = CVar_Get("s_showUpdates", "0", CVAR_CHEAT, "Show number of emitter updates");
	s_showEmitters = CVar_Get("s_showEmitters", "0", CVAR_CHEAT, "Show sound emitters activity");
	s_showChannels = CVar_Get("s_showChannels", "0", CVAR_CHEAT, "Show number of active channels");
	s_showPortalChecks = CVar_Get("s_showPortalChecks", "0", CVAR_CHEAT, "Show number of portal occlusion checks");
	s_skipStreaming = CVar_Get("s_skipStreaming", "0", CVAR_CHEAT, "Skip playing streaming sounds");
	s_skipUpdates = CVar_Get("s_skipUpdates", "0", CVAR_CHEAT, "Skip emitter updates, making everything static");
	s_skipEmitters = CVar_Get("s_skipEmitters", "0", CVAR_CHEAT, "Skip playing sound emitters");
	s_skipShakes = CVar_Get("s_skipShakes", "0", CVAR_CHEAT, "Skip sound shakes");
	s_skipAttenuation = CVar_Get("s_skipAttenuation", "0", CVAR_CHEAT, "Skip distance attenuation");
	s_skipOcclusion = CVar_Get("s_skipOcclusion", "0", CVAR_CHEAT, "Skip occlusion effects");
	s_skipReverb = CVar_Get("s_skipReverb", "0", CVAR_CHEAT, "Skip reverb effects");
	s_alDriver = CVar_Get("s_alDriver", AL_DRIVER_OPENAL, CVAR_ARCHIVE | CVAR_LATCH, "AL driver");
	s_ignoreALErrors = CVar_Get("s_ignoreALErrors", "1", CVAR_ARCHIVE, "Ignore AL errors");
	s_deviceName = CVar_Get("s_deviceName", "", CVAR_ARCHIVE | CVAR_LATCH, "Sound device name");
	s_masterVolume = CVar_Get("s_masterVolume", "1.0", CVAR_ARCHIVE, "Master volume");
	s_effectsVolume = CVar_Get("s_effectsVolume", "1.0", CVAR_ARCHIVE, "Effects volume");
	s_musicVolume = CVar_Get("s_musicVolume", "1.0", CVAR_ARCHIVE, "Music volume");
	s_dopplerFactor = CVar_Get("s_dopplerFactor", "1.0", CVAR_ARCHIVE, "Doppler factor for moving sound emitters");
	s_efxOcclusion = CVar_Get("s_efxOcclusion", "1", CVAR_ARCHIVE | CVAR_LATCH, "Use EFX occlusion effects");
	s_efxReverb = CVar_Get("s_efxReverb", "1", CVAR_ARCHIVE | CVAR_LATCH, "Use EFX reverb effects");
	s_maxChannels = CVar_Get("s_maxChannels", "0", CVAR_ARCHIVE | CVAR_LATCH, "Maximum number of mixed channels (0 = unlimited)");
	s_maxSoundsPerShader = CVar_Get("s_maxSoundsPerShader", "0", CVAR_ARCHIVE | CVAR_LATCH, "Maximum number of sounds per shader (0 = unlimited)");
	s_maxSampleRate = CVar_Get("s_maxSampleRate", "0", CVAR_ARCHIVE | CVAR_LATCH, "Maximum sound sample rate (0 = default sample rate)");

	Cmd_AddCommand("listSoundDevices", S_ListSoundDevices_f, "Lists sound devices");
	Cmd_AddCommand("sfxInfo", S_SfxInfo_f, "Shows sound information");
}

/*
 ==================
 S_Unregister
 ==================
*/
static void S_Unregister (void){

	Cmd_RemoveCommand("listSoundDevices");
	Cmd_RemoveCommand("sfxInfo");
}

/*
 ==================
 S_Init
 ==================
*/
void S_Init (bool all){

	Com_Printf("------- Sound Initialization -------\n");

	if (all){
		S_Register();

		ALImp_Init();
	}

	// Set active
	snd.active = true;

	// Clear listener area
	snd.listener.area = -1;

	// Initialize all the sound modules
	S_InitSounds();
	S_InitSoundShaders();
	S_InitEFX();
	S_InitStreaming();
	S_InitChannels();
	S_InitEmitters();

	if (!s_ignoreALErrors->integerValue)
		S_CheckForErrors();

	Com_Printf("------------------------------------\n");
}

/*
 ==================
 S_Shutdown
 ==================
*/
void S_Shutdown (bool all){

	if (!alConfig.initialized)
		return;

	S_ShutdownEmitters();
	S_ShutdownChannels();
	S_ShutdownStreaming();
	S_ShutdownEFX();
	S_ShutdownSoundShaders();
	S_ShutdownSounds();

	if (all){
		S_Unregister();

		ALImp_Shutdown();
	}

	Mem_TagFree(TAG_SOUND);

	memset(&snd, 0, sizeof(sndGlobals_t));
}
